package SPA;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.Scanner;
import java.util.regex.MatchResult;
import java.util.regex.Pattern;

import Lexical.Tag;
import Lexical.Token;
import Lexical.Word;
import Util.HashTable;


/**
 * SPA Parser
 */
public class Parser 
{	
	private SPA spa = new SPA();		// Structured Pushdown Automata
	private String path;
	
	private HashTable aux = new HashTable();
	
	/**
	 * Constructor
	 * @param path	path of automata files
	 * @param root	root-automaton
	 * @throws FileNotFoundException 
	 */
	public Parser(String path, String root) throws FileNotFoundException
	{	
		this.path = path;
		spa.addAutomaton(parseFile(root));
	}
	
	/**
	 * Gets Structured Pushdown Automata
	 * @return spa
	 */
	public SPA getSPA()
	{
		return this.spa;
	}
	
	private DFA parseFile(String automatonName) throws FileNotFoundException
	{
		// verify if automaton is already on the stack
		if (this.spa.getAutomaton(automatonName) != null)
		{
			return this.spa.getAutomaton(automatonName);
		}
		// else, verifiy if automaton is on the aux list
		else if (this.aux.getValue(automatonName) != null)
		{
			return (DFA)this.aux.getValueAndRemove(automatonName);
		}
		// if none, start parsing
		else
		{
			Scanner scanner = new Scanner(new FileReader(new File(this.path + "/" + automatonName.toLowerCase() + ".txt")));
			
			DFA automaton = new DFA(parseName(scanner.nextLine()), parseInitialState(scanner.nextLine()));
			parseFinalStates(scanner.nextLine(), automaton);
			
			try 
			{
				while (scanner.hasNextLine()) 
				{
					parseTransition(scanner.nextLine(), automaton);
				}
			} 
			catch (Exception e) 
			{
				e.printStackTrace();
			} 
			finally 
			{
				scanner.close();
			}
			
			return automaton;	
		}	
	}
	
	private String parseName(String line)
	{
		Scanner scanner = new Scanner(line);
		scanner.useDelimiter("name: ");
		return scanner.next().toLowerCase();
	}
	
	private DFAState parseInitialState(String line)
	{
		Scanner scanner = new Scanner(line);
		scanner.useDelimiter("initial: ");
		
		return new DFAState(scanner.nextInt());
	}
	
	private void parseFinalStates(String line, DFA automaton)
	{
		Scanner scanner = new Scanner(line);
		scanner.useDelimiter("final: ");
		String[] finals = scanner.next().trim().split(", ");
		
		for (int i = 0; i < finals.length; i++)
		{
			if(automaton.getState(Integer.parseInt(finals[i])) == null)
				automaton.addState(new DFAState(Integer.parseInt(finals[i]), true));
			else
				automaton.getState(Integer.parseInt(finals[i])).setFinal(true);
		}
	}
	
	private void parseTransition(String line, DFA automaton) throws Exception 
	{
		Scanner scanner = new Scanner(line);
		Pattern pattern1 = Pattern.compile("\\((\\d+), \"([\\w\\[\\]\\(\\)\\:\\;\\=\\,]+)\"\\) -> (\\d+) & (\\w+)");
		Pattern pattern2 = Pattern.compile("\\((\\d+), (\\w+)\\) -> (\\d+) & (\\w+)");
					
		// State Transition
		if (scanner.findInLine(pattern1) != null)
		{
			MatchResult result = scanner.match();			
						
			DFAState si, so;
			Token tk = null;
			for (Tag t : Tag.values())
			{
				if(t.isReserved())
				{
					if (t.getName().toUpperCase().equals(result.group(2).toUpperCase().replace("\"", "")))
					{
						tk = new Token(t);
					}					
				}
				
				else
				{
					if (t.name().equals(result.group(2).toUpperCase().replace("\"", "")))
					{
						tk = new Token(t);
					}					
				}				
			}
			
			if (tk == null)
			{
				tk = new Word(Tag.SYMBOL, result.group(2).toUpperCase().replace("\"", ""));
			}
			
			// Verifies if states already exists in the automaton
			if (automaton.getState(Integer.parseInt(result.group(1))) == null)
				automaton.addState(new DFAState(Integer.parseInt(result
						.group(1))));

			if (automaton.getState(Integer.parseInt(result.group(3))) == null)
				automaton.addState(new DFAState(Integer.parseInt(result
						.group(3))));

			// Get si and so (initial and end states of the transition)
			si = automaton.getState(Integer.parseInt(result.group(1)));
			so = automaton.getState(Integer.parseInt(result.group(3)));

			// Get the semantic action
			String semanticAction = (!result.group(4).equalsIgnoreCase("NULL")) ? result.group(4).toUpperCase() : null; 
			
			// Add the state transition
			si.addTransition(new StateTransition(tk, so, semanticAction));

		}
		
		// Submachine Call Transition
		else if (scanner.findInLine(pattern2) != null)
		{
			MatchResult result = scanner.match();
			
			DFAState si, so;			
			// Verifies if states already exists in the automaton
			if (automaton.getState(Integer.parseInt(result.group(1))) == null)
				automaton.addState(new DFAState(Integer.parseInt(result.group(1))));
			
			if (automaton.getState(Integer.parseInt(result.group(3))) == null)
				automaton.addState(new DFAState(Integer.parseInt(result.group(3))));
			
			// Get si and so (initial and end states of the transition)
			si = automaton.getState(Integer.parseInt(result.group(1)));
			so = automaton.getState(Integer.parseInt(result.group(3)));	
			
			// Get the semantic action
			String semanticAction = (!result.group(4).equalsIgnoreCase("NULL")) ? result.group(4).toUpperCase() : null;
			
			// Verify if it's a recursive call
			if (automaton.getName() == result.group(2).toLowerCase())
			{
				si.addTransition(new SubmachineCallTransition(automaton, so, semanticAction));
			}
			
			else
			{
				aux.put(automaton.getName(), automaton);
				DFA subAutomaton = parseFile(result.group(2).toLowerCase());
				spa.addAutomaton(subAutomaton);
				
				si.addTransition(new SubmachineCallTransition(subAutomaton, so, semanticAction));
			}
		}
		else
		{
			throw new Exception("ERROR on parsing DFA (" + automaton.getName() + "): Pattern doesn't match. ( " + line + " )");
		}
		
		scanner.close();
	}
	
	public static void main(String[] args) throws FileNotFoundException
	{
		final String path = "../compilador/src/Grammar";
		Parser parser = new Parser(path, "element");
		System.out.print(parser.getSPA());
	}
	
	
}
